Skip to content

[DREAM-693] Unify and document ExpandableTextComponent - #23328

Merged
myabc merged 20 commits into
devfrom
code-maintenance/75274-document-expandable-text
Jun 17, 2026
Merged

[DREAM-693] Unify and document ExpandableTextComponent#23328
myabc merged 20 commits into
devfrom
code-maintenance/75274-document-expandable-text

Conversation

@myabc

@myabc myabc commented May 22, 2026

Copy link
Copy Markdown
Contributor

Ticket

https://community.openproject.org/wp/DREAM-693

What are you trying to accomplish?

Introduces a reusable ExpandableTextComponent and collapses the two overlapping truncation Stimulus controllers into a single, private expandable-text controller.

The component truncates block content either horizontally (one line, via Primer::Beta::Truncate) or vertically (multi-line, via the new OpPrimer::VerticalTruncateComponent), exposing an expander that reveals the full text inline or opens a dialog it owns. AttributeComponent is recomposed on top of it (choosing the truncation direction from its lines count) and the legacy attribute.controller.ts is removed.

Screenshots

the behaviour is best seen ib the new ExpandableText Lookbook page (horizontal, vertical, in-table, dialog, and playground previews).

Horizontal

Inline

Not expanded Expanded
Screenshot 2026-06-16 at 17 10 28 Screenshot 2026-06-16 at 17 10 39

Inline - within table cells

Not expanded Expanded
Screenshot 2026-06-16 at 17 17 17 Screenshot 2026-06-16 at 17 17 11

Vertical

Inline

image

Dialog

image

What approach did you choose and why?

A single Stimulus controller replaces the duplicated logic, so horizontal/vertical truncation and inline/dialog expansion share one code path. The component exposes two orthogonal axes — direction: (:horizontal/:vertical) and expansion: (:inline/:dialog) — validated through Primer's fetch_or_fallback, and is documented in Lookbook.

In :dialog mode the component owns the dialog through a dialog slot: it generates the dialog id, wires the expander to open it, and renders a default dialog when no slot is supplied, so callers never hand-set show_dialog_id. The trigger also advertises the dialog to assistive technology (aria-haspopup/aria-controls); aria-expanded is kept on the inline (disclosure) variant only.

The expandable-text controller is private to ExpandableTextComponent: it operates on the DOM the component renders (including Primer::Beta::Truncate's internal .Truncate-text), so it is not a reusable behavior.

The new preview spec registers the Lookbook preview path with a small shim because Lookbook is disabled in the test environment; a cleaner mechanism is left for a follow-up.

Vertical expander alignment. In vertical mode the expander is a flex sibling cross-aligned to the last visible line (align_items: :flex_end), which keeps -webkit-line-clamp truncation detection simple and robust. A bottom-right wrap-around expander (text flowing around the control on the last line) was considered but deferred — it would mean dropping line-clamp for a max-height clamp plus either per-mode markup divergence (float-spacer) or reviving the removed absolute-overlay + text-hider machinery. To be discussed in review.

Merge checklist

  • Added/updated tests
  • Added/updated documentation in Lookbook (patterns, previews, etc)
  • Tested major browsers (Chrome, Firefox, Edge, ...)

@myabc myabc changed the title code maintenance/75274 document expandable text [#75274] Unify and document ExpandableTextComponent May 22, 2026
@myabc myabc added maintenance needs review styling ruby Pull requests that update Ruby code and removed needs review labels May 22, 2026
Base automatically changed from bug/75275-truncated-text-hellip-misalignment to release/17.5 May 26, 2026 12:28
@myabc
myabc changed the base branch from release/17.5 to dev May 26, 2026 14:50
@github-actions

github-actions Bot commented May 26, 2026

Copy link
Copy Markdown

Deploying openproject with PullPreview

Field Value
Latest commit 852b075
Job deploy
Status ✅ Deploy successful
Preview URL https://pr-23328-75274-document-e-ip-167-235-250-91.my.opf.run:443

View logs

@myabc
myabc force-pushed the code-maintenance/75274-document-expandable-text branch from 8d912e3 to c4c0187 Compare May 29, 2026 20:16
@myabc
myabc requested a review from Copilot May 29, 2026 20:17
@myabc
myabc marked this pull request as ready for review May 29, 2026 20:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR standardizes truncation/expansion behavior around OpPrimer::ExpandableTextComponent, adds Lookbook documentation/previews for it, and migrates OpenProject::Common::AttributeComponent from a bespoke Stimulus/CSS implementation to the shared truncation controller + component.

Changes:

  • Extend the Stimulus truncation controller to support horizontal vs vertical truncation and inline vs dialog-style expansion.
  • Enhance OpPrimer::ExpandableTextComponent (API + docs) and add Lookbook previews/spec coverage.
  • Refactor OpenProject::Common::AttributeComponent to compose ExpandableTextComponent, removing the legacy attribute controller and styles.

Reviewed changes

Copilot reviewed 20 out of 20 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
spec/components/open_project/common/inplace_edit_fields/display_fields/rich_text_area_component_spec.rb Updates expectations to the unified truncation controller / expander visibility.
spec/components/op_primer/expandable_text_component_spec.rb Expands component specs to cover modes, options, and validation.
spec/components/op_primer/expandable_text_component_preview_spec.rb Adds preview rendering specs (including Lookbook path shim).
lookbook/previews/op_primer/expandable_text_component_preview.rb Adds preview class with multiple scenarios + playground params.
lookbook/previews/op_primer/expandable_text_component_preview/default.html.erb Default horizontal example template.
lookbook/previews/op_primer/expandable_text_component_preview/in_table.html.erb Table layout example template.
lookbook/previews/op_primer/expandable_text_component_preview/vertical.html.erb Vertical line-clamp example template.
lookbook/previews/op_primer/expandable_text_component_preview/dialog.html.erb Dialog expansion example template.
lookbook/previews/op_primer/expandable_text_component_preview/playground.html.erb Param-driven playground template.
lookbook/docs/components/expandable-text.md.erb Adds component documentation and usage guidance.
frontend/src/stimulus/controllers/truncation.controller.ts Adds mode/inline values and vertical truncation + class toggling.
frontend/src/stimulus/controllers/truncation.controller.spec.ts Updates/expands unit tests for new controller behavior.
frontend/src/stimulus/controllers/dynamic/attribute.controller.ts Removes legacy attribute truncation controller.
frontend/src/global_styles/content/_text_utils.sass Adds .expandable-text--expanded to undo line-clamp when expanded.
app/components/open_project/common/attribute_component.rb Drops legacy params and adapts to new expander logic.
app/components/open_project/common/attribute_component.html.erb Replaces bespoke truncation markup with ExpandableTextComponent.
app/components/open_project/common/attribute_component.sass Removes legacy CSS for the old attribute truncation implementation.
app/components/op_primer/expandable_text_component.rb Documents/unifies component API; wires controller values and expander args.
app/components/op_primer/expandable_text_component.html.erb Adds vertical rendering path and uses unified expander arguments.
app/components/_index.sass Removes import of deleted legacy attribute component stylesheet.

Comment thread frontend/src/stimulus/controllers/expandable-text.controller.ts
Comment thread app/components/op_primer/expandable_text_component.html.erb Outdated
Comment thread app/components/op_primer/expandable_text_component.rb Outdated
Comment thread app/components/op_primer/expandable_text_component.rb
@myabc
myabc force-pushed the code-maintenance/75274-document-expandable-text branch 2 times, most recently from 7ae6625 to 376bc80 Compare May 29, 2026 22:01
@myabc
myabc requested a review from Copilot May 29, 2026 22:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 20 out of 20 changed files in this pull request and generated 1 comment.

Comment thread app/components/op_primer/expandable_text_component.rb
Comment thread spec/components/op_primer/expandable_text_component_preview_spec.rb
@myabc
myabc force-pushed the code-maintenance/75274-document-expandable-text branch from 376bc80 to c2f0ee7 Compare May 30, 2026 15:28
myabc added 3 commits May 30, 2026 17:32
Two controllers solved the same truncation-detection problem
independently. Unifies them with orthogonal truncation/expansion axes on
`ExpandableTextComponent`.

Extends `TruncationController` with `mode` (horizontal/vertical) and
`inline` (true/false) values. Vertical detection uses `scrollHeight` vs
`clientHeight`, working with `-webkit-line-clamp`.

Extends `ExpandableTextComponent` with `truncation:`, `lines:`,
`inline:`, and `expander_arguments:` params. Adds
`expandable-text--expanded` CSS class for vertical inline expansion.

https://community.openproject.org/wp/75274
`AttributeComponent` now uses `ExpandableTextComponent` with
`truncation: :vertical, inline: false` instead of managing its own
truncation markup. Deletes the now-redundant `AttributeController`
Stimulus controller and text-hider overlay.

https://community.openproject.org/wp/75274
Adds the ExpandableTextComponent Lookbook preview covering the
horizontal, vertical, short-text, in-table, dialog, and playground
variants and documenting each truncation mode, plus a spec that
smoke-tests every preview renders.

https://community.openproject.org/work_packages/75274
@myabc
myabc force-pushed the code-maintenance/75274-document-expandable-text branch from c2f0ee7 to da299b9 Compare May 30, 2026 15:32
@myabc
myabc requested a review from Copilot May 30, 2026 15:42

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 21 out of 21 changed files in this pull request and generated 1 comment.

Comment thread app/components/open_project/common/attribute_component.html.erb Outdated
@github-actions

Copy link
Copy Markdown

Warning

Flaky specs

  • rspec ./modules/backlogs/spec/features/backlogs/start_finish_spec.rb[1:2:2:3]
  • rspec ./modules/team_planner/spec/features/query_handling_spec.rb[1:3:1:1]

myabc added 2 commits June 16, 2026 16:40
In dialog mode the expander opens a modal dialog but did not announce
that to assistive technology, and HellipButton's hardcoded
`aria-expanded` wrongly implied an in-place disclosure. The button now
carries `aria-haspopup="dialog"` and `aria-controls`, and the controller
drops `aria-expanded` for the dialog variant (it stays for inline).
For now this component is only used internally. This may change in the
future.
@myabc
myabc requested a review from HDinger June 16, 2026 14:49
Replaces raw CSS attribute selectors: the `aria-label` check uses the
`aria:` filter, and the class-free data-attribute checks use
`have_element` with the attributes as options.
@myabc
myabc force-pushed the code-maintenance/75274-document-expandable-text branch from 125d790 to 1a92b29 Compare June 16, 2026 15:00
@myabc myabc changed the title [SDS-693] Unify and document ExpandableTextComponent [DREAM-693] Unify and document ExpandableTextComponent Jun 16, 2026
@myabc myabc added this to the 17.6.x milestone Jun 16, 2026
Comment thread app/components/op_primer/expandable_text_component.html.erb
} else {
// The button opens a modal dialog (aria-haspopup="dialog"), so the
// disclosure-style aria-expanded that HellipButton hardcodes does not apply.
this.expanderButton.removeAttribute('aria-expanded');

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@HDinger @bsatarnejad we should consider making this optional in upstream HellipButton.

@HDinger HDinger left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lookbook docs are much better readable by now 👍

Comment thread app/components/op_primer/expandable_text_component.html.erb
Comment thread app/components/op_primer/expandable_text_component.rb Outdated
Comment thread lookbook/docs/components/expandable-text.md.erb Outdated
Comment on lines +1 to +11
<div style="max-width: <%= width %>px;">
<%= render(OpPrimer::ExpandableTextComponent.new(direction:, lines:, expansion:)) do |component| %>
<% if expansion == :dialog %>
<% component.with_dialog(title: "Full text", size: :large) do |dialog| %>
<% dialog.with_header(variant: :large) %>
<% dialog.with_body_content(text) %>
<% end %>
<% end %>
<%= text %>
<% end %>
</div>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still have one problem with this preview: When choosing horizontal as the direction, the expansion state still goes to the bottom, which is the same as when chosing vertical. I always expect that things open to the right which does not work because the container has a max-width which cannot grow. This is very confusing and I keep stumbling over it even though I know the component by now..

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@HDinger I agree that this was confusing - and would likely trip up devs implementing to this. horizontal/vertical in this case referred to the truncation direction, not the expansion direction.

I've now renamed direction: :horizontal|:verticaltruncate: :single_line|:multi_line. This should make things much clearer!

@@ -0,0 +1,11 @@
<div style="max-width: <%= width %>px;">

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the benefit of making that configurable? Imho, it just complicates the preview

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shows the behaviour with different max-widths, which I think is useful:

Screen.Recording.2026-06-17.at.19.00.34.mov

Having said that, I've had issues with the range input not staying in sync with the query params (this might be a Lookbook issue?)

http://localhost:3000/lookbook/inspect/OpenProject/Primer/expandable_text/playground?width=110&text=Not+too+much+text

| `horizontal` (default) | A single line, cut off with an ellipsis (`…`) | Short labels in table cells or list rows — permission names, statuses |
| `vertical` | The first `lines:` rows, then clamped | Multi-line descriptions or rich-text previews |

`vertical` takes a `lines:` count (1–6, default 3):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is somehow lost here.. Either you make it a real secton###vertical or add as a notes column to the table.
What do you mean with 1-6? Is the max number of lines 6? What if I need more?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought 6 was an OK number for most use cases. I can bump it to 8 just in case.

The reason for imposing a hard limit is that we generate a modifier class for each number of lines.. so op-vertical-truncate--lines-1 to --lines-6. Obviously we don't want to bloat our CSS with an infinite number line numbers. One option would be to use CSS variables instead, e.g. by setting style="--expandable-text-lines:6" as an attribute.

That would be more scalable, but I know you're also weary of inline styles 😃 .. it is also a different approach to the one used in frontend/src/global_styles/content/_text_utils.sass.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@HDinger I've created a separate follow-up PR here: #23800 - in case you prefer that method

myabc added 4 commits June 17, 2026 16:28
Names the `lines: 3` default `LINES_DEFAULT` for consistency with
the existing `DIRECTION_DEFAULT` and `EXPANSION_DEFAULT` constants.
Lookbook strips the full `ComponentPreview` suffix, so the inspect
path is `expandable_text`, not `expandable_text_component`.
The `direction: :horizontal|:vertical` option named the truncation
axis but read as the expansion direction, so callers expected
`:horizontal` to expand rightward. Renames it to
`truncate: :single_line|:multi_line`, which describes the collapsed
output and decouples it from how the text expands. The Stimulus
`mode` value and the `multi_line` preview track the same terms.
@github-actions

Copy link
Copy Markdown

Warning

Flaky specs

  • rspec ./modules/team_planner/spec/features/team_planner_overview_spec.rb[1:4:4:1]
🤖 Ask Copilot to investigate

Copy the prompt below into a new comment on this PR to delegate the investigation to GitHub Copilot. It will look into the flakiness and open a separate pull request with you as reviewer.

@copilot The following spec(s) are flaky in CI (first seen on PR #23328, linked for reference only):

- `rspec ./modules/team_planner/spec/features/team_planner_overview_spec.rb[1:4:4:1]`

Treat this as a standalone task, unrelated to PR #23328. Create a new branch from origin/dev and open a new pull request targeting dev — do not stack it on PR #23328 or reuse that branch.

Follow the playbook in docs/development/testing/handling-flaky-tests/README.md to find the root cause and fix the underlying race — do not skip, delete, or weaken the spec to make it pass; disabling is a last resort per the playbook, and only with a bug ticket. Verify the fix by running the spec(s) repeatedly (e.g. `script/bulk_run_rspec --run-count 10`).

If you cannot reproduce the flake or are not confident in a fix after reasonable investigation, do not fabricate a change or skip the spec to force CI green. Instead, leave the pull request in draft and document what you tried, the suspected cause, and any leads in its description, then assign @myabc to take over.

Once the fix is verified, title the PR after the spec(s) it fixes, and use the PR description to explain the root cause, how the change resolves it, and the before/after results. Label the PR `flaky-spec`, assign @myabc, and request a review from @myabc.
On every commit, set @myabc as the sole co-author with a `Co-authored-by:` trailer (use their GitHub no-reply email so it links to their account), so it is traceable who dispatched the fix.

@myabc

myabc commented Jun 17, 2026

Copy link
Copy Markdown
Contributor Author

@HDinger I think I've addressed the most important you addressed in the latest review, so I will go ahead and merge as-is. I'm keen to tie up this loose end for 17.6. Obviously I'm still open to iterating on the API and behaviour in the future..

@myabc
myabc requested a review from HDinger June 17, 2026 17:15
myabc added 2 commits June 17, 2026 19:15
Raises the supported `lines:` count from 6 to 8. The limit is hard
because each value generates a `--lines-N` CSS modifier class, so the
range stays bounded rather than open-ended.
Raises the `lines:` lower bound from 1 to 2. A single clamped line is
indistinguishable from `single_line`, so allowing it only invited
confusion; one line is `Primer::Beta::Truncate`'s job.
@myabc myabc added javascript Pull requests that update Javascript code and removed needs review pullpreview labels Jun 17, 2026
@github-actions

Copy link
Copy Markdown

Warning

Flaky specs

  • rspec ./spec/features/projects/project_autocomplete_spec.rb[1:1]
  • rspec ./spec/features/roles/report_spec.rb[1:1]
🤖 Ask Copilot to investigate

Copy the prompt below into a new comment on this PR to delegate the investigation to GitHub Copilot. It will look into the flakiness and open a separate pull request with you as reviewer.

@copilot The following spec(s) are flaky in CI (first seen on PR #23328, linked for reference only):

- `rspec ./spec/features/projects/project_autocomplete_spec.rb[1:1]`
- `rspec ./spec/features/roles/report_spec.rb[1:1]`

Treat this as a standalone task, unrelated to PR #23328. Create a new branch from origin/dev and open a new pull request targeting dev — do not stack it on PR #23328 or reuse that branch.

Follow the playbook in docs/development/testing/handling-flaky-tests/README.md to find the root cause and fix the underlying race — do not skip, delete, or weaken the spec to make it pass; disabling is a last resort per the playbook, and only with a bug ticket. Verify the fix by running the spec(s) repeatedly (e.g. `script/bulk_run_rspec --run-count 10`).

If you cannot reproduce the flake or are not confident in a fix after reasonable investigation, do not fabricate a change or skip the spec to force CI green. Instead, leave the pull request in draft and document what you tried, the suspected cause, and any leads in its description, then assign @myabc to take over.

Once the fix is verified, title the PR after the spec(s) it fixes, and use the PR description to explain the root cause, how the change resolves it, and the before/after results. Label the PR `flaky-spec`, assign @myabc, and request a review from @myabc.
On every commit, set @myabc as the sole co-author with a `Co-authored-by:` trailer (use their GitHub no-reply email so it links to their account), so it is traceable who dispatched the fix.

@myabc
myabc merged commit d8da8d6 into dev Jun 17, 2026
21 checks passed
@myabc
myabc deleted the code-maintenance/75274-document-expandable-text branch June 17, 2026 18:30
@github-actions github-actions Bot locked and limited conversation to collaborators Jun 17, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

javascript Pull requests that update Javascript code maintenance ruby Pull requests that update Ruby code styling

Development

Successfully merging this pull request may close these issues.

3 participants